/**
 * TDS PEC 4
 * Grupo 3H2M
 *
 * El paquete <code>edu.uoc.tds.pec4.server.bbdd</code> agrupa todos los
 * interfaces y clases que encapsulan los objetos de acceso a datos (DAO),para
 * realizar el acceso a datos. La base de datos es PostgreSQL.
 */
package edu.uoc.tds.pec4.server.bbdd;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; 
import java.util.Properties;

/** Importar librerias edu.uoc.tds.pec4.server.bbdd.exceptions.DataBaseDAOException */
import edu.uoc.tds.pec4.server.bbdd.exceptions.DataBaseDAOException;
import edu.uoc.tds.pec4.common.util.TDSLanguageUtils;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * La clase <code>DataBaseDAO</code> hospeda la conexión a la base de datos. Permite
 * la realización de conexiones a la base de datos. El SGBD es PostgreSQL, 
 * versión 8 en adelante. <br />
 * De esta clase heredan todas aquellas que actúen como objetos DAO. <br />
 * La primera clase que la instancia carga el driver y realiza la conexión. <br />
 * 
 * @author      Luis Rodríguez-Manzaneque Sánchez
 * @author      Isabel Guerra Monclova
 * @version     03/11/2011
 * @since       1.00
 * @see         edu.uoc.tds.pec4.server.bbdd.exceptions.DataBaseDAOException
 */
public class DataBaseDAO {

    /** propertiesFile Fichero de configuración, con su ruta absoluta. */
    private static final String propertiesFile = "properties/configuration.properties";
    /** driver Driver de acceso a datos */
    private static final String driver = "org.postgresql.Driver";
    /** driverLoaded Nos informa si el driver ya ha sido cargado */
    private static boolean driverLoaded = false;
    /** objConn Objeto de conexión a la base de datos */
    private static Connection objConn = null;
    private Statement objStm = null;
    private ResultSet objRS = null;

    /** 
     * Credenciales para acceder a datos
     */
    private static String sUrl = null;
    private static String sUserName = null;
    private static String sPassword = null;

    /**
     * Constructor de la clase, invoca la carga del driver de la base de datos. </ br>
     * La primera vez que se instancie la clase se cargará el driver. El resto de
     * las ocasiones no será necesario. </ br>
     * @throws  DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    public DataBaseDAO() throws DataBaseDAOException {
        //System.out.println("Me llama " + this.getClass().toString() + ".DataBaseDAO");
        cargarDriver();
        openConn();
    }

    /**
     * Método estático que primero lee el archivo de propiedades, y posteriormente
     * carga el driver de conexión a la base de datos. </ br>
     * La primera vez que se instancie la clase se cargará el driver. El resto de
     * las ocasiones no será necesario. </ br>
     * @throws  DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    private static void cargarDriver() throws DataBaseDAOException {
        //System.out.println("DataBaseDAO.cargarDriver - ¿hay que cargarlo?");
        if (!driverLoaded) {
            //System.out.println("DataBaseDAO.cargarDriver - Cargamos el driver");
            Properties objProperties = new Properties();

            // 1. Cargamos el fichero de propiedades
            try {
                FileInputStream objFile = new FileInputStream(propertiesFile);
                objProperties.load(objFile);
                sUrl = objProperties.getProperty("url");
                sUserName = objProperties.getProperty("username");
                sPassword = objProperties.getProperty("password");               
                
            } catch (FileNotFoundException eFnfe) {
                // Error: El fichero no se ha encontrado
                throw new DataBaseDAOException(DataBaseDAOException.FILE_PROP_CAN_NOT_READ,eFnfe);
            } catch (IOException eIo) {
                // Error de lectura
                throw new DataBaseDAOException(DataBaseDAOException.FILE_PROP_CAN_NOT_READ,eIo);
            } catch (Exception eV) {
                // Error desconocido
                throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_CONNECT_DB,eV);
            }

            // 2. Cargamos el driver de acceso a datos
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException eCnfe) {
                // No se puede cargar el driver de acceso a la base de datos
                throw new DataBaseDAOException(DataBaseDAOException.DRIVER_NOT_FOUND, eCnfe);
            }

            driverLoaded = true;
        }
    }

    /**
     * Abre la conexión a la base de datos
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    private void openConn() throws DataBaseDAOException {
        // Realizamos la conexión a la base de datos
        //System.out.println("DataBaseDAO.openConn - ¿hay que habrir la conexion?");
        if (objConn == null) {
            try {
                //System.out.println("DataBaseDAO.openConn - Abrimos la conexion");
                objConn = DriverManager.getConnection(sUrl, sUserName, sPassword);
            } catch (SQLException eSQL) {
                // Error al conectar con la base de datos
                throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_CONNECT_DB, eSQL);
            }
        }
    }

    /**
     * Activa o desactiva la transacción.
     * @param   valor El flag que indica si la transacción se activa
     * @throws  DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected void setAutoCommit(boolean valor) throws DataBaseDAOException {
        try {
            objConn.setAutoCommit(valor);
        }catch (SQLException eSQL) {
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_AUTOCOMMIT, eSQL);
        }
    }

    /**
     * Realiza el commit de la transacción activa
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected void commit() throws DataBaseDAOException {
        try {
            objConn.commit();
        }catch (SQLException eSQL) {
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_COMMIT, eSQL);
        }
    }

    /**
     * Realiza el rollback de la transacción activa
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected void rollback() throws DataBaseDAOException {
        try {
            objConn.rollback();
        }catch (SQLException eSQL) {
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_ROLLBACK, eSQL);
        }
    }

    /**
     * Construye un preparedStatement, y lo devuelve.
     * @param sSQL La sentencia SQL de la que queremos construir un preparedStatement
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected PreparedStatement preparedStatement(String sSQL) throws DataBaseDAOException {
        try {
            return objConn.prepareStatement(sSQL);

        }catch (SQLException eSQL) {
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_PREPAREDSTATEMENT, eSQL);
        }
    }

    /**
     * Lanza una consulta de selección contra la base de datos, y el resultado
     * lo almacena en un resultset.
     * @param sSQL  La consulta que se debe ejecutar.
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected ResultSet obtenerConsulta(String sSQL) throws DataBaseDAOException {

        try {
            objStm = objConn.createStatement();
            objRS = objStm.executeQuery(sSQL);
        } catch (SQLException ex) {
            // Error al obtener una consulta
            ex.printStackTrace();
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_EXECUTEQUERY, ex);
        }
        
        return objRS;
    }

    /**
     * Realiza el cierre de todos los objetos implicados en la conexión contra la
     * base de datos. Se comprueba que existe cada objeto, y si es así primero
     * cierra el ResultSet, luego el Statement y finalmente la Connection.
     * @throws DataBaseDAOException En caso de generarse una excepción se dispara
     *          al nivel superior
     */
    protected void closeConn() throws DataBaseDAOException {
        try {
            if (objRS != null) {
                //System.out.println("DataBaseDAO.closeConn - cerramos objRS");
                objRS.close();
                objRS = null;
            }
            if (objStm != null) {
                //System.out.println("DataBaseDAO.closeConn - cerramos objStm");
                objStm.close();
                objStm = null;
            }
            if (objConn != null) {
                //System.out.println("DataBaseDAO.closeConn - cerramos objConn");
                objConn.close();
                objConn = null;
            }
        } catch (SQLException ex) {
            // Error al intentar cerrar la BD
            throw new DataBaseDAOException(DataBaseDAOException.CAN_NOT_CLOSE, ex);
        }
    }

    @Override
    protected void finalize() throws Throwable {
        // Cerramos la conexión con la base de datos
        closeConn();
    }

    /**
     * main sólo válido para efectos de depuración
     */
    public static void main(String[] args) throws DataBaseDAOException {
        if (!TDSLanguageUtils.setDefaultLanguage("i18n/messages")) {
            System.out.println("No se ha encontrado el fichero de mensajes 'i18n/messages*.properties'. No se puede arrancar.");
            System.exit(-1);
        }

        DataBaseDAO gestor = new DataBaseDAO();
        gestor.openConn();
        System.out.println(objConn);
    }
}